﻿/// <reference path="../References/UI/Bounds.ts" />
/// <reference path="../Core.ts" />

module Supernova {

    export function handleWindowResized() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        aspect = canvas.width / canvas.height;
        var screenBounds = UI.Bounds.create(canvas);
        if (UI.root !== null) {
            UI.compute(UI.root, screenBounds);
        }
    };

    export function worldToScreen(worldX: number, worldY: number): IPoint {
        var zoom = Math.pow(ZOOM_POWER, MAX_ZOOM_LEVEL - galaxy.currentView.zoom);
        var relX = worldX - galaxy.currentView.x;
        var relY = worldY - galaxy.currentView.y;
        var x = relX * zoom * canvas.width + canvas.width / 2;
        var y = aspect * relY * zoom * canvas.height + canvas.height / 2;
        var result = { x: Math.floor(x), y: Math.floor(y) };
        return result;
    }

    export function worldToScreenLength(worldLength: number): number {
        var zoom = Math.pow(ZOOM_POWER, MAX_ZOOM_LEVEL - galaxy.currentView.zoom);
        var screenLength = worldLength * zoom * canvas.width;
        return Math.floor(screenLength);
    }

    export function screenToWorld(screenX: number, screenY: number): IZoomPoint {
        var zoom = Math.pow(ZOOM_POWER, MAX_ZOOM_LEVEL - galaxy.currentView.zoom);
        var x = (screenX / canvas.width - 0.5) / zoom + galaxy.currentView.x;
        var y = ((screenY / canvas.height - 0.5) / aspect) / zoom + galaxy.currentView.y;
        var result = { x: x, y: y, zoom: galaxy.currentView.zoom };
        return result;
    }

    export function screenToWorldLength(screenLength: number): number {
        var zoom = Math.pow(ZOOM_POWER, MAX_ZOOM_LEVEL - galaxy.currentView.zoom);
        return screenLength / canvas.width / zoom;
    }

    export function drawScene(pendingClick: IPoint): ClickedItem {
        var clickedItem = new ClickedItem();

        context.clearRect(0, 0, canvas.width, canvas.height);
        context.textBaseline = 'middle';

        // Is there a galaxy?    
        if (galaxy === null) {
            if (Options.showStarfield) starField.render();
            Notices.renderMessages();
        } else {
            frames += 1;

            // Move the view around
            var differenceView = {
                x: galaxy.targetView.x - galaxy.currentView.x,
                y: galaxy.targetView.y - galaxy.currentView.y,
                zoom: galaxy.targetView.zoom - galaxy.currentView.zoom
            };
            galaxy.currentView.x += differenceView.x / VIEW_MOVEMENT_ITERATIONS;
            galaxy.currentView.y += differenceView.y / VIEW_MOVEMENT_ITERATIONS;
            galaxy.currentView.zoom += differenceView.zoom / VIEW_ZOOM_ITERATIONS;

            // Update sizes
            var zoomRatio = galaxy.currentView.zoom / MAX_ZOOM_LEVEL;
            sizes.star = Math.floor(10 + zoomRatio * 25);
            sizes.body = Math.floor(4 + zoomRatio * 15);
            sizes.ship = Math.floor(6 + zoomRatio * 5);
            sizes.pod = Math.floor(2 + zoomRatio * 2.5);
            sizes.missile = Math.floor(1 + zoomRatio * 2);
            sizes.artillery = Math.floor(2 + zoomRatio * 3);
            sizes.orbital = Math.floor(2 + zoomRatio * 3);

            // Perform useful pre-calculations
            for (var shipKey in galaxy.ships) {
                var ship = galaxy.ships[shipKey];
                var currentLocation = worldToScreen(ship.currentLocation.x, ship.currentLocation.y);
                ship.calculated.x = currentLocation.x;
                ship.calculated.y = currentLocation.y;
            }

            // Draw ship and artillery radii
            drawBackgroundRadii();

            // Draw starfield
            if (Options.showStarfield) starField.render();

            // Draw messages
            Notices.renderMessages();

            // Render star systems
            for (var i = 0; i < galaxy.starSystems.length; i++) {
                var system = galaxy.starSystems[i];
                drawStarSystem(system);
            }

            // Draw wrecks
            context.strokeStyle = '#666';
            context.fillStyle = '#111';
            context.lineWidth = 1;
            context.beginPath();
            for (var j in galaxy.wrecks) {
                var wreck = galaxy.wrecks[j];
                if (wreck.visible) {
                    var location = worldToScreen(wreck.x, wreck.y);
                    drawShipShape(location.x, location.y, sizes.ship, OwnerType.human, 1, 0);
                }
            }
            context.closePath();
            context.fill();
            context.stroke();

            // Draw artillery
            if (frames % 2 == 0) drawArtillery();

            // Draw ships
            drawShips(OwnerType.human);
            if (!channel.online) {
                drawShips(OwnerType.computer);
            } else {
                for (var j in channel.players) {
                    var jInt = parseInt(j);
                    if (jInt != channel.playerCode) {
                        drawShips(OwnerType.computer - jInt);
                    }
                }
            }

            // Draw particles
            drawParticles();

            // Highlight selected ships
            if (selected.ships.length > 0) {
                for (var shipKey in selected.ships) {
                    var ship = selected.ships[shipKey];

                    // Highlight the ship
                    context.fillStyle = playerLookup[ship.owner].shipColor;
                    context.beginPath();
                    drawShipShape(ship.calculated.x, ship.calculated.y, sizes.ship, ship.owner, ship.attack, ship.bombard);
                    context.closePath();
                    context.fill();

                    // Draw ship's path
                    if ((ship.owner == OwnerType.human || galaxy.mode != GameMode.multiplayer) && ship.currentOrder !== null) {
                        context.lineWidth = 2;
                        context.strokeStyle = playerLookup[ship.owner].shipColor;
                        context.beginPath();
                        context.moveTo(ship.calculated.x, ship.calculated.y);
                        if (ship.currentOrder.targetLocation !== null) {
                            var firstPoint = worldToScreen(ship.currentOrder.targetLocation.x, ship.currentOrder.targetLocation.y);
                            context.lineTo(firstPoint.x, firstPoint.y);
                        }
                        for (var i = 0; i < ship.orders.length; i++) {
                            var order = ship.orders[i];
                            if (order.targetLocation !== null) {
                                order.targetLocation.updateXY();
                                var nextPoint = worldToScreen(order.targetLocation.x, order.targetLocation.y);
                                context.lineTo(nextPoint.x, nextPoint.y);
                            }
                        }
                        context.stroke();
                    }
                }
            }

            // Draw selection
            if (selection !== null && (selection.x1 != selection.x2 || selection.y1 != selection.y2)) {
                var box = convertToBox(selection.x1, selection.y1, selection.x2, selection.y2);
                context.lineWidth = 3;
                context.strokeStyle = '#eee';
                context.strokeRect(box.left, box.top, box.width, box.height);
            }

            // Draw toast
            Notices.renderToasts();

            // Draw stats for selected ship or body
            if (selected.ships.length > 0) {
                drawSelectedShips(pendingClick, clickedItem);
            } else if (selected.body !== null) {
                drawSelectedBody();
            } else if (selected.system !== null) {
                drawSelectedSystem();
            }
        }
        return clickedItem;
    }

    function drawBackgroundRadii() {

        // Ship scanning radius
        context.fillStyle = '#111';
        for (var shipKey in galaxy.ships) {
            var ship = galaxy.ships[shipKey];
            if (ship.owner == OwnerType.human || galaxy.mode == GameMode.replay) {
                if (ship.scanning > 0) {
                    var radius = worldToScreenLength(ship.scanning);
                    fillCircle(ship.calculated.x, ship.calculated.y, radius);
                }
            }
        }

        // Body scanning radius
        for (var i = 0; i < galaxy.starSystems.length; i++) {
            var system = galaxy.starSystems[i];
            if (system.owner == OwnerType.human || galaxy.mode == GameMode.replay) {
                for (var j = 1; j < system.bodies.length; j++) {
                    var body = system.bodies[j];
                    if (body.population > 0) {
                        var bodyLocation = worldToScreen(body.x, body.y);
                        var scannerRange = galaxy.research.passive.efficientScanners ? config.scanning.range.withFacility * 1.25 : config.scanning.range.withFacility;
                        var range = body.facilities.scanner ? scannerRange : config.scanning.range.noFacility;
                        var radius = worldToScreenLength(range);
                        fillCircle(bodyLocation.x, bodyLocation.y, radius);
                    }
                }
            }
        }

        // Body artillery radius
        for (var i = 0; i < galaxy.starSystems.length; i++) {
            var system = galaxy.starSystems[i];
            if (system.owner == OwnerType.human || galaxy.mode == GameMode.replay) {
                for (var j = 1; j < system.bodies.length; j++) {
                    var body = system.bodies[j];
                    if (frames % 6 < 3 && body.facilities.artillery !== null && body.population > 0) {
                        context.strokeStyle = '#ddd';
                        var bodyLocation = worldToScreen(body.x, body.y);
                        strokeCircle(bodyLocation.x, bodyLocation.y, worldToScreenLength(galaxy.research.artillery.planetary.radius));
                    }
                }
            }
        }

        // Ship attack radius
        if (galaxy.mode != GameMode.replay) {
            for (var shipKey in galaxy.ships) {
                var ship = galaxy.ships[shipKey];
                if (ship.visible && ship.attackRadius > 0 && ship.owner <= OwnerType.computer) {
                    context.fillStyle = '#411';
                    var radius = worldToScreenLength(ship.attackRadius);
                    fillCircle(ship.calculated.x, ship.calculated.y, radius);
                }
            }
        }
        for (var shipKey in galaxy.ships) {
            var ship = galaxy.ships[shipKey];
            if (ship.attackRadius > 0 && (ship.owner == OwnerType.human || galaxy.mode == GameMode.replay)) {
                context.fillStyle = '#200';
                var radius = worldToScreenLength(ship.attackRadius);
                fillCircle(ship.calculated.x, ship.calculated.y, radius);
            }
        }

    }

    function drawSelectedShips(pendingClick: IPoint, clickedItem: ClickedItem) {
        context.textAlign = 'left';
        context.font = '15px Consolas';
        var x = gameUI.leftPanel.left;
        var y = gameUI.buildShipyardButton.computed.bottom + 8;
        for (var shipKey in selected.ships) {
            var ship = selected.ships[shipKey];
            context.fillStyle = playerLookup[ship.owner].shipColor;

            // Is the user clicking the area that we're currently drawing?
            if (pendingClick != null && pendingClick.x >= x && pendingClick.y >= y - 3 && pendingClick.x <= x + 44 && pendingClick.y <= y + 3) {
                clickedItem.ship = ship;
            }

            if ((ship.owner == OwnerType.human || galaxy.mode == GameMode.replay) && ship.preset !== null) {
                var stats = [];
                if (ship.preset.attack > config.initialSlots.attack) stats.push('Attack ' + ship.preset.attack);
                if (ship.preset.armor > config.initialSlots.armor) stats.push('Armor ' + ship.preset.armor);
                if (ship.preset.shields > config.initialSlots.shields) stats.push('Shields ' + ship.preset.shields);
                if (ship.preset.bombard > config.initialSlots.bombard) stats.push('Invade ' + ship.preset.bombard);
                if (ship.preset.population > config.initialSlots.colony) stats.push('Population ' + ship.preset.population);
                if (ship.preset.speed > config.initialSlots.speed) stats.push('Speed ' + ship.preset.speed);
                if (ship.preset.scanning > config.initialSlots.scanner) stats.push('Scanning ' + ship.preset.scanning);
                if (ship.preset.terraform > config.initialSlots.terraforming) stats.push('Terraformer');
                if (ship.preset.mining > config.initialSlots.mining) stats.push('Mining ' + ship.preset.mining);
                var text = ship.name;
                if (stats.length > 0) text += ' (' + stats.join(', ') + ')';
                context.fillStyle = playerLookup[ship.owner].shipColor;
                context.fillText(text, x + 48, y);
                context.strokeStyle = 'silver';
                context.fillStyle = 'silver';
                drawProgressBar(x, y - 3, 44, 6, ship.armor / ship.maxArmor);
                y += 18;
            } else {
                context.fillText(ship.name, x, y);
                y += 18;
            }
        }
    }

    function drawSelectedBody() {
        var body = selected.body;
        context.textAlign = 'left';
        context.font = '15px Consolas';
        context.fillStyle = playerLookup[body.system.owner].shipColor;
        var x = gameUI.leftPanel.left;
        var y = gameUI.buildShipyardButton.computed.bottom + 8;
        context.fillText(body.system.owner == OwnerType.human ? 'Human owned planet' : body.system.owner <= OwnerType.computer ? 'Enemy owned Planet' : 'Unclaimed planet', x, y);
        y += 25;
        context.fillText('Type:       ' + (body.type == BodyType.barren ? 'Barren' : body.type == BodyType.habitable ? 'Habitable' : 'Gas giant'), x, y);
        y += 18;
        if (body.system.owner <= OwnerType.computer && body.type == BodyType.habitable) {
            context.fillText('Population: 🚶 ' + body.population, x, y);
            y += 18;
        }
        var mySystem = body.system.owner == OwnerType.human || galaxy.mode == GameMode.replay;
        if (mySystem && body.type == BodyType.habitable) {
            context.fillText('Population: 🚶 ' + body.population + ' / ' + body.maxPopulation, x, y);
            y += 18;
            var facilities = body.describeInstalledFacilities();
            if (facilities != 'None') {
                context.fillText('Facilities: 🏠 ' + body.describeInstalledFacilities(), x, y);
                y += 18;
            }
        }
        if (mySystem && body.population > 0) {
            var goldRate = body.calculateGoldRate(true);
            var scienceRate = body.calculateScienceRate();
            context.fillText('Gold:       ◍ ' + goldRate.toFixed(2) + '/s', x, y);
            y += 18;
            context.fillText('Science:    ⓢ ' + scienceRate.toFixed(2) + '/s', x, y);
            y += 18;
        }
        if (body.gold > 0) {
            context.fillText('Resources:  Gold (◍ ' + Math.floor(body.gold / 100) / 10 + ' K)', x, y);
            y += 18;
        }
    }

    function drawSelectedSystem() {
        var system = selected.system;
        context.textAlign = 'left';
        context.font = '15px Consolas';
        var x = gameUI.leftPanel.left;
        var y = gameUI.buildShipyardButton.computed.bottom + 8;
        if (system.discovered) {
            context.fillStyle = playerLookup[system.owner].shipColor;
            context.fillText(system.name != '' ? system.name + ' (Explored)' : 'System (Explored)', x, y);
            y += 25;
        } else {
            context.fillStyle = playerLookup[0].shipColor;
            context.fillText('Unexplored system', x, y);
            return;
        }
        var suffix = 'with ' + (system.bodies.length - 1) + ' planets';
        var systemType = system.owner == OwnerType.human ? 'Human owned system ' + suffix : system.owner <= OwnerType.computer ? 'Enemy owned system ' + suffix : 'Unclaimed system ' + suffix;
        if (system.bodies.length <= 1) systemType = 'Star with no planets';
        context.fillText(systemType, x, y);
        y += 18;
        var foundBarren = false;
        for (var i = 0; i < system.bodies.length; i++) {
            if (system.bodies[i].type == BodyType.barren) foundBarren = true;
        }
        var mySystem = system.owner == OwnerType.human || galaxy.mode == GameMode.replay;
        if (mySystem) {
            if (foundBarren) {
                context.fillText('Population: 🚶 ' + system.population, x, y);
                y += 18;
                context.fillText('Terraform all planets to reveal maximum possible population', x, y);
                y += 18;
            } else {
                context.fillText('Population: 🚶 ' + system.population + ' / ' + system.maxPopulation, x, y);
                y += 18;
            }
        } else if (system.owner <= OwnerType.computer) {
            context.fillText('Population: 🚶 ' + system.population, x, y);
            y += 18;
        }
        if (mySystem && system.population > 0) {
            var goldRate = 0;
            var scienceRate = 0;
            for (var i = 0; i < system.bodies.length; i++) {
                var body = system.bodies[i];
                goldRate += body.calculateGoldRate(true);
                scienceRate += body.calculateScienceRate();
            }
            context.fillText('Gold:       ◍ ' + goldRate.toFixed(2) + '/s', x, y);
            y += 18;
            context.fillText('Science:    ⓢ ' + scienceRate.toFixed(2) + '/s', x, y);
            y += 18;
        }
    }

    function drawParticles() {
        for (var particleKey in particles) {
            var particle = particles[particleKey];
            var p = worldToScreen(particle.x, particle.y);
            var distance = particle.life * particle.speed;
            context.fillStyle = particle.color;
            fillCircle(p.x + distance * Math.cos(particle.angle), p.y + distance * Math.sin(particle.angle), particle.radius);
            particle.life++;
            if (particle.life >= particle.death) {
                delete particles[particleKey];
            }
        }
    }

    function drawArtillery() {
        for (var i in galaxy.artillery) {
            var artillery = galaxy.artillery[i];
            var color = playerLookup[artillery.owner].starColor;
            context.fillStyle = color;
            var location = worldToScreen(artillery.x, artillery.y);
            fillCircle(location.x, location.y, sizes.artillery);
        }
    }

    function drawShips(owner: OwnerType) {
        var color = playerLookup[owner].shipColor;

        // Draw missiles
        context.lineWidth = 2;
        context.strokeStyle = color;
        context.fillStyle = color;
        context.font = '14px verdana';
        context.beginPath();
        for (var missileKey in galaxy.missiles) {
            var missile = galaxy.missiles[missileKey];
            if (missile.sourceShip.owner == owner) {
                var missileLocation = worldToScreen(missile.x, missile.y);
                context.moveTo(missileLocation.x - sizes.missile, missileLocation.y - sizes.missile);
                context.lineTo(missileLocation.x + sizes.missile, missileLocation.y - sizes.missile);
                context.lineTo(missileLocation.x + sizes.missile, missileLocation.y + sizes.missile);
                context.lineTo(missileLocation.x - sizes.missile, missileLocation.y + sizes.missile);
                context.lineTo(missileLocation.x - sizes.missile, missileLocation.y - sizes.missile);
            }
        }
        context.closePath();
        context.stroke();

        // Create a list of the ships we're going to draw, so that we don't keep enumerating the whole set
        var ships: Array<Ship> = [];
        for (var i in galaxy.ships) {
            var ship = galaxy.ships[i];
            if (ship.owner == owner && (ship.visible || galaxy.mode == GameMode.replay)) ships.push(ship);
        }

        // Draw the ships themselves    
        context.beginPath();
        for (var i in ships) {
            var ship = ships[i];
            drawShipShape(ship.calculated.x, ship.calculated.y, sizes.ship, ship.owner, ship.attack, ship.bombard);
        }
        context.closePath();
        context.stroke();

        // Draw ship progress status
        if (galaxy.currentView.zoom >= ZOOM_THRESHOLD_SHIP_BARS) {
            for (var i in ships) {
                var ship = ships[i];
                var landed = ship.currentLocation.body !== null;
                var bombard = ship.bombard > 0 && landed && ship.currentLocation.body.system.owner != ship.owner && ship.currentLocation.body.population > 0;
                var terraform = ship.terraforming > 0 && landed && (ship.currentLocation.body.system.owner == ship.owner || ship.currentLocation.body.system.owner == OwnerType.none) && (ship.currentLocation.body.type == BodyType.barren || (ship.currentLocation.body.type == BodyType.gas && galaxy.research.actions.terraformGasGiants));
                if (ship.owner == owner && (bombard || terraform)) {
                    var startAngle = -Math.PI / 2;
                    var ratio = bombard ? ship.bombardWait / ship.maxBombardWait : ship.terraformingWait / TERRAFORM_WAIT;
                    var endAngle = startAngle + (1 - ratio) * 2 * Math.PI;
                    context.lineWidth = ratio <= 0 ? 6 : 3;
                    if (ratio > 0 || frames % 10 <= 4) {
                        strokeArc(ship.calculated.x, ship.calculated.y, sizes.ship + 7, startAngle, endAngle);
                    }
                }
            }
        }

        // Draw the name of each ship
        if (galaxy.currentView.zoom >= ZOOM_THRESHOLD_SHIP_NAME) {
            for (var i in ships) {
                var ship = ships[i];
                var name = ship.name;
                if (ship.formation) name += ' (F)';
                if (ship.currentOrder !== null && ship.currentOrder.type == ShipOrderType.repair) name += ' (Repairing)';
                context.fillText(name, ship.calculated.x, ship.calculated.y - sizes.ship - 8);
            }
        }

        // Draw ship bars
        context.lineWidth = 1;
        context.strokeStyle = 'silver';
        if (galaxy.currentView.zoom >= ZOOM_THRESHOLD_SHIP_BARS) {
            for (var i in ships) {
                var ship = ships[i];
                var barY = ship.calculated.y - sizes.ship - 26;

                // Population
                if (ship.population > 0) {
                    context.fillStyle = 'yellow';
                    var populationSize = ship.population * 10;
                    drawProgressBar(ship.calculated.x - populationSize, barY, populationSize * 2, 6, 1, ship.population);
                    barY -= 8;
                }

                // Terraforming
                if (ship.terraforming > 0) {
                    var w = Math.max(10, ship.population * 10);
                    context.fillStyle = 'green';
                    drawProgressBar(ship.calculated.x - w, barY, w * 2, 6, ship.terraforming);
                    barY -= 8;
                }

                // Armor
                context.fillStyle = 'silver';
                var armorSize = 10 + Math.min(90, ship.maxArmor);
                drawProgressBar(ship.calculated.x - armorSize, barY, armorSize * 2, 6, ship.armor / ship.maxArmor);
                barY -= 8;

                // Shields
                if (ship.maxShields > 0) {
                    context.fillStyle = 'blue';
                    var shieldSize = 10 + Math.min(90, ship.maxShields);
                    drawProgressBar(ship.calculated.x - shieldSize, barY, shieldSize * 2, 6, ship.shields / ship.maxShields);
                    barY -= 8;
                }
            }
        }

        // Draw artillery pods
        context.fillStyle = color;
        for (var i in ships) {
            var ship = ships[i];
            if (ship.artilleryPods > 0) {
                var x = ship.calculated.x - ship.artilleryPods / 2 * (sizes.pod * 2 + 4) + sizes.pod + 2;
                for (var j = 0; j < ship.artilleryPods; j++) {
                    if (ship.artilleryPodWait > 0 || frames % 2 == 0) {
                        fillCircle(x, ship.calculated.y + sizes.ship * 2, sizes.pod);
                        x += (sizes.pod * 2 + 4);
                    }
                }
            }
        }

    }

    function drawStarSystem(system: StarSystem) {
        
        // Can we skip drawing the star itself?
        var systemLocation = worldToScreen(system.x, system.y);
        if (systemLocation.x + sizes.star >= 0 && systemLocation.y + sizes.star >= 0 && systemLocation.x - sizes.star < canvas.width && systemLocation.y - sizes.star < canvas.height) {

            // Draw the star        
            var color = '#444';
            if (system.discovered) {
                color = playerLookup[system.owner].starColor;
            }
            if (system.bodies.length <= 1 && system.discovered) {
                context.strokeStyle = color;
                strokeCircle(systemLocation.x, systemLocation.y, sizes.star);
            } else {
                context.fillStyle = color;
                fillCircle(systemLocation.x, systemLocation.y, sizes.star);
            }

            // Is this the selected star system?
            if (selected.system == system) {
                context.strokeStyle = 'white';
                strokeCircle(systemLocation.x, systemLocation.y, sizes.star + 3);
            }

            // Draw star system details
            if (system.discovered && galaxy.currentView.zoom >= ZOOM_THRESHOLD_SYSTEM_DETAILS) {
                context.textAlign = 'center';
                context.fillStyle = 'white';
                context.font = '14px verdana';
                context.fillText(system.name, systemLocation.x, systemLocation.y + sizes.star + 6);
            }
        }
        
        // Draw orbits
        if (Options.showOrbitLines) {
            context.strokeStyle = '#333';
            context.lineWidth = 1;
            if (galaxy.currentView.zoom >= ZOOM_THRESHOLD_ORBITS_VISIBLE && system.discovered) {
                for (var i = 1; i < system.bodies.length; i++) {
                    var body = system.bodies[i];
                    var starLocation = worldToScreen(system.x, system.y);
                    var bodyLocation = worldToScreen(system.x + body.distance, system.y);

                    // Can we skip drawing this orbit?
                    var distance = calculateDistance(bodyLocation.x, bodyLocation.y, starLocation.x, starLocation.y);
                    if (starLocation.x + distance >= 0 && starLocation.y + distance >= 0 && starLocation.x - distance < canvas.width && starLocation.y - distance < canvas.height) {
                        strokeCircle(starLocation.x, starLocation.y, distance);
                    }
                }
            }
        }

        // Draw bodies in star system
        context.strokeStyle = '#bbb';
        context.textAlign = 'center';
        if (galaxy.currentView.zoom >= ZOOM_THRESHOLD_BODIES_VISIBLE && system.discovered) {
            for (var i = 1; i < system.bodies.length; i++) {
                var body = system.bodies[i];
                drawBody(body);
            }
        }
    }

    function drawBody(body: Body) {

        // Can we skip this?
        var bodyLocation = worldToScreen(body.x, body.y);
        if (bodyLocation.x + sizes.body < 0 || bodyLocation.y + sizes.body < 0 || bodyLocation.x - sizes.body >= canvas.width || bodyLocation.y - sizes.body >= canvas.height) return;

        // Draw the body and the body details        
        if (body.type == BodyType.habitable) context.fillStyle = '#8f9';
        if (body.type == BodyType.barren) context.fillStyle = '#b63';
        if (body.type == BodyType.gas) context.fillStyle = '#78e';
        fillCircle(bodyLocation.x, bodyLocation.y, sizes.body);

        // Is this the selected body?
        if (selected.body == body) {
            context.strokeStyle = 'white';
            strokeCircle(bodyLocation.x, bodyLocation.y, sizes.body + 3);
        }

        // Show additional details if zoomed in far enough
        if (galaxy.currentView.zoom >= ZOOM_THRESHOLD_BODY_DETAILS) {

            // Draw orbitals
            var y = bodyLocation.y + sizes.body + 10;
            for (var i = 0; i < body.facilities.orbitals; i++) {
                var angle = ((Math.PI * 2) / body.facilities.orbitals) * i - Math.PI / 2;
                var orbitalX = bodyLocation.x + Math.cos(angle) * (sizes.body + sizes.orbital);
                var orbitalY = bodyLocation.y + Math.sin(angle) * (sizes.body + sizes.orbital);
                fillCircle(orbitalX, orbitalY, sizes.orbital);
            }
            if (body.facilities.orbitals > 1) {
                y += sizes.orbital + 4;
            }

            // Draw population progress bar
            context.font = '10px verdana';
            if (body.type == BodyType.habitable && body.maxPopulation > 0) {
                if (body.population > 0 && body.population < body.maxPopulation && (body.system.owner == OwnerType.human || galaxy.mode == GameMode.replay)) {
                    drawProgressBar(bodyLocation.x - 40, y, 80, 8, body.populationProgress, 4);
                    y += 16;
                }

                // Draw population and facility indicators
                var populationText = '🚶 ' + (body.system.owner == OwnerType.human || body.system.owner == OwnerType.none || galaxy.mode == GameMode.replay ? body.population + ' / ' + body.maxPopulation : body.population.toString());
                var facilitiesText = '';
                if (body.facilities.shipyard) facilitiesText += 'S';
                if (body.facilities.comms) facilitiesText += 'C';
                if (body.facilities.science) facilitiesText += 'R';
                if (body.facilities.mine) facilitiesText += 'M';
                if (body.facilities.artillery) facilitiesText += 'A';
                context.fillText(facilitiesText == '' ? populationText : populationText + ' - ' + facilitiesText, bodyLocation.x, y);
                y += 12;
            }
            
            // Draw resource indicator
            if (body.gold > 0) {
                var goldText = '◍ ' + Math.floor(body.gold / 100) / 10 + ' K';
                context.fillText(goldText, bodyLocation.x, y);
                y += 12;
            }

            // Draw goodies
            if (body.goodies.gold > 0) {
                context.fillStyle = '#ddf';
                context.fillText('+ ◍ ' + body.goodies.gold, bodyLocation.x, y);
                y += 12;
            }
            if (body.goodies.science > 0) {
                context.fillStyle = '#ddf';
                context.fillText('+ ⓢ ' + body.goodies.science, bodyLocation.x, y);
                y += 12;
            }
        }
    }

    function drawShipShape(x: number, y: number, size: number, owner: OwnerType, attack: number, bombard: number) {
        if (attack == 0 && bombard == 0) {
            context.moveTo(x - size, y - size);
            context.lineTo(x + size, y - size);
            context.lineTo(x + size, y + size);
            context.lineTo(x - size, y + size);
            context.lineTo(x - size, y - size);
        } else if (bombard > 0 && (galaxy.mode != GameMode.singlePlayer || owner == OwnerType.human)) {
            context.moveTo(x - size, y - size);
            context.lineTo(x + size, y - size);
            context.lineTo(x + (size / 2) | 0, y);
            context.lineTo(x + size, y + size);
            context.lineTo(x - size, y + size);
            context.lineTo(x - (size / 2) | 0, y);
            context.lineTo(x - size, y - sizes.ship);
        } else {
            context.moveTo(x, y - sizes.ship);
            context.lineTo(x + size, y + size);
            context.lineTo(x - size, y + size);
            context.lineTo(x, y - sizes.ship);
        }
    }

    function drawProgressBar(x: number, y: number, width: number, height: number, ratio: number, segmentCount: number = undefined) {
        context.fillRect(x, y, ratio * width, height);
        if (segmentCount === undefined) {
            context.strokeRect(x, y, width, height);
        } else {
            var segmentWidth = width / segmentCount;
            for (var i = 0; i < segmentCount; i++) {
                context.strokeRect(x + i * segmentWidth, y, segmentWidth, height);
            }
        }
    }

    export function addParticles(x: number, y: number, count: number, life: number, radius: number, speed: number, color: string) {
        for (var i = 0; i < count; i++) {
            particles.push({ x: x, y: y, life: 0, death: life, radius: radius, angle: Math.random() * 2 * Math.PI, speed: Math.random() * speed, color: color });
        }
    }

    function strokeArc(x: number, y: number, radius: number, startAngle: number, endAngle: number) {
        context.beginPath();
        context.arc(x, y, radius, startAngle, endAngle, false);
        context.stroke();
    }

    function strokeCircle(x: number, y: number, radius: number) {
        context.beginPath();
        context.arc(x, y, radius, 0, 2 * Math.PI, false);
        context.closePath();
        context.stroke();
    }

    function fillCircle(x: number, y: number, radius: number) {
        context.beginPath();
        context.arc(x, y, radius, 0, 2 * Math.PI, false);
        context.closePath();
        context.fill();
    }

}